/*
 *    Copyright (c) 2018-2025, nart All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 * Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 * Neither the name of the pig4cloud.com developer nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 * Author: nart
 */
package com.nai365.nart.ware.service.impl;

import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.nai365.nart.common.core.constant.OrderConstant;
import com.nai365.nart.common.core.constant.SecurityConstants;
import com.nai365.nart.common.core.exception.NoStockException;
import com.nai365.nart.common.core.to.OrderTO;
import com.nai365.nart.common.core.to.SkuHasStockTO;
import com.nai365.nart.common.core.util.R;
import com.nai365.nart.ware.constant.WareOrderTaskConstant;
import com.nai365.nart.ware.entity.WareOrderTask;
import com.nai365.nart.ware.entity.WareOrderTaskDetail;
import com.nai365.nart.ware.entity.WareSku;
import com.nai365.nart.ware.feign.OrderFeignService;
import com.nai365.nart.ware.feign.ProductFeignService;
import com.nai365.nart.ware.mapper.WareSkuMapper;
import com.nai365.nart.ware.service.WareOrderTaskService;
import com.nai365.nart.ware.service.WareSkuService;
import com.nai365.nart.ware.to.*;
import com.nai365.nart.ware.vo.OrderItemVO;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.stream.Collectors;

/**
 * 商品库存
 *
 * @author baogs
 * @date 2022-07-02 13:21:19
 */
@Service
public class WareSkuServiceImpl extends ServiceImpl<WareSkuMapper, WareSku> implements WareSkuService {


	@Autowired
	ProductFeignService productFeignService;
	@Autowired
	WareOrderTaskService orderTaskService;
	@Autowired
	WareOrderTaskDetailServiceImpl orderTaskDetailService;
	@Autowired
	OrderFeignService orderFeignService;
	@Autowired
	RabbitTemplate rabbitTemplate;


	/**
	 * 库存入库
	 */
	@Transactional
	@Override
	public void addStock(Long skuId, Long ownerId, Integer skuNum) {
		Long count = baseMapper.selectCount(new QueryWrapper<WareSku>().eq("sku_id", skuId).eq("owner_id", ownerId));
		if (count > 0) {
			// 当前仓库有当前商品库存，更新库存
			baseMapper.addStock(skuId, ownerId, skuNum);
		} else {
			// 当前仓库无当前商品库存，新增记录
			WareSku wareSku = new WareSku();
			wareSku.setSkuId(skuId);
			wareSku.setOwnerId(ownerId);
			wareSku.setStock(skuNum);
			wareSku.setStockLocked(0);
			// 冗余字段
			R info = productFeignService.info(skuId, SecurityConstants.FROM_IN);
			SkuInfoTO skuInfoTO = JSONUtil.toBean(JSONUtil.toJsonStr(info.getData()), SkuInfoTO.class);
			if (skuInfoTO != null) {
				wareSku.setSkuName(skuInfoTO.getSkuName());
			}
			baseMapper.insert(wareSku);
		}
	}

	/**
	 * 查询sku是否有库存
	 */
	@Override
	public List<SkuHasStockTO> getSkusHasStock(List<Long> skuIds) {
		return skuIds.stream().map(skuId -> {
			SkuHasStockTO stock = new SkuHasStockTO();
			// 查询当前sku总库存量
			Long count = baseMapper.getSkusStock(skuId);
			stock.setSkuId(skuId);
			stock.setHasStock(count == null ? false : count > 0);
			return stock;
		}).collect(Collectors.toList());
	}

	/**
	 * 库存锁定，sql执行锁定锁定
	 *
	 * @param lockTO
	 * @return 锁定结果
	 * @Transactional(rollbackFor = NoStockException.class)：指定的异常出现会导致回滚
	 * 未指定异常，任何运行时异常都会导致回滚，可以省略rollbackFor
	 */
	@Transactional
	@Override
	public Boolean orderLockStock(WareSkuLockTO lockTO) {
		// 按照收货地址找到就近仓库，锁定库存（暂未实现）
		// 采用方案：获取每项商品在哪些仓库有库存，轮询尝试锁定，任一商品锁定失败回滚

		// 1.往库存工作单存储当前锁定（本地事务表）
		WareOrderTask taskEntity = new WareOrderTask();
		taskEntity.setOrderSn(lockTO.getOrderSn());
		orderTaskService.save(taskEntity);

		// 2.封装待锁定库存项Map
		Map<Long, OrderItemVO> lockItemMap = lockTO.getLocks().stream().collect(Collectors.toMap(key -> key.getSkuId(), val -> val));
		// 3.查询（库存 - 库存锁定 >= 待锁定库存数）的仓库
		List<WareSku> wareEntities = baseMapper.selectListHasSkuStock(lockItemMap.keySet()).stream().filter(entity -> entity.getStock() - entity.getStockLocked() >= lockItemMap.get(entity.getSkuId()).getCount()).collect(Collectors.toList());
		// 判断是否查询到仓库
		if (CollectionUtils.isEmpty(wareEntities)) {
			// 匹配失败，所有商品项没有库存
			Set<Long> skuIds = lockItemMap.keySet();
			throw new NoStockException(skuIds);
		}
		// 将查询出的仓库数据封装成Map，key:skuId  val:wareId
		Map<Long, List<WareSku>> wareMap = wareEntities.stream().collect(Collectors.groupingBy(key -> key.getSkuId()));
		// 4.判断是否为每一个商品项至少匹配了一个仓库
		List<WareOrderTaskDetail> taskDetails = new ArrayList<>();// 库存锁定工作单详情
		Map<Long, StockLockedTO> lockedMessageMap = new HashMap<>();// 库存锁定工作单消息
		if (wareMap.size() < lockTO.getLocks().size()) {
			// 匹配失败，部分商品没有库存
			Set<Long> skuIds = lockItemMap.keySet();
			skuIds.removeAll(wareMap.keySet());// 求商品项差集
			throw new NoStockException(skuIds);
		} else {
			// 所有商品都存在有库存的仓库
			// 5.锁定库存
			for (Map.Entry<Long, List<WareSku>> entry : wareMap.entrySet()) {
				Boolean skuStocked = false;
				Long skuId = entry.getKey();// 商品
				OrderItemVO item = lockItemMap.get(skuId);
				Integer count = item.getCount();// 待锁定个数
				List<WareSku> hasStockWares = entry.getValue();// 有足够库存的仓库
				for (WareSku ware : hasStockWares) {
					Long num = baseMapper.lockSkuStock(skuId, ware.getOwnerId(), count);
					if (num == 1) {
						// 锁定成功，跳出循环
						skuStocked = true;
						// 创建库存锁定工作单详情（每一件商品锁定详情）
						WareOrderTaskDetail taskDetail = new WareOrderTaskDetail(null, skuId,
								item.getTitle(), count, taskEntity.getId(), ware.getOwnerId(),
								WareOrderTaskConstant.LockStatusEnum.LOCKED.getCode());
						taskDetails.add(taskDetail);
						// 创建库存锁定工作单消息（每一件商品一条消息）
						StockDetailTO detailMessage = new StockDetailTO();
						BeanUtils.copyProperties(taskDetail, detailMessage);
						StockLockedTO lockedMessage = new StockLockedTO(taskEntity.getId(), detailMessage);
						lockedMessageMap.put(skuId, lockedMessage);
						break;
					}
				}
				if (!skuStocked) {
					// 匹配失败，当前商品所有仓库都未锁定成功
					throw new NoStockException(skuId);
				}
			}
		}

		// 6.往库存工作单详情存储当前锁定（本地事务表）
		orderTaskDetailService.saveBatch(taskDetails);

		// 7.发送消息
		for (WareOrderTaskDetail taskDetail : taskDetails) {
			StockLockedTO message = lockedMessageMap.get(taskDetail.getSkuId());
			message.getDetail().setId(taskDetail.getId());// 存储库存详情ID
			rabbitTemplate.convertAndSend("stock-event-exchange", "stock.locked", message);
		}
		return true;
	}

	/**
	 * 库存解锁
	 */
	@Override
	public void unLockStock(StockLockedTO locked) throws Exception {
		StockDetailTO taskDetailTO = locked.getDetail();// 库存工作单详情TO
		WareOrderTaskDetail taskDetail = orderTaskDetailService.getById(taskDetailTO.getId());// 库存工作单详情Entity
		if (taskDetail != null) {
			// 1.工作单未回滚，需要解锁
			WareOrderTask task = orderTaskService.getById(locked.getId());// 库存工作单Entity
			R r = orderFeignService.getOrderByOrderSn(task.getOrderSn(), SecurityConstants.FROM_IN);// 订单Entity
			if (r.getCode() == 0) {
				// 订单数据返回成功
//				OrderTO order =  r.getData(r.getData(),new TypeReference<OrderTO>() {
//				});
				OrderTO order = JSONUtil.toBean(JSONUtil.toJsonStr(r.getData()), OrderTO.class);
				if (order == null || OrderConstant.OrderStatusEnum.CANCLED.getCode().equals(order.getStatus())) {
					// 2.订单已回滚 || 订单未回滚已取消状态
					if (WareOrderTaskConstant.LockStatusEnum.LOCKED.getCode().equals(taskDetail.getLockStatus())) {
						// 订单已锁定状态，需要解锁（消息确认）
						unLockStock(taskDetailTO.getSkuId(), taskDetailTO.getOwnerId(), taskDetailTO.getSkuNum(), taskDetailTO.getId());
					} else {
						// 订单其他状态，不可解锁（消息确认）
					}
				}
			} else {
				// 订单远程调用失败（消息重新入队）
				throw new RuntimeException();
			}
		} else {
			// 3.无库存锁定工作单记录，已回滚，无需解锁（消息确认）
		}
	}

	/**
	 * 库存解锁
	 * 订单解锁触发，防止库存解锁消息优先于订单解锁消息到期，导致库存无法解锁
	 */
	@Transactional
	@Override
	public void unLockStock(OrderTO order) {
		String orderSn = order.getOrderSn();// 订单号
		// 1.根据订单号查询库存锁定工作单
		WareOrderTask task = orderTaskService.getOrderTaskByOrderSn(orderSn);
		// 2.按照工作单查询未解锁的库存，进行解锁
		List<WareOrderTaskDetail> taskDetails = orderTaskDetailService.list(new QueryWrapper<WareOrderTaskDetail>()
				.eq("task_id", task.getId())
				.eq("lock_status", WareOrderTaskConstant.LockStatusEnum.LOCKED.getCode()));// 并发问题
		// 3.解锁库存
		for (WareOrderTaskDetail taskDetail : taskDetails) {
			unLockStock(taskDetail.getSkuId(), taskDetail.getOwnerId(), taskDetail.getSkuNum(), taskDetail.getId());
		}
	}

	/**
	 * 库存解锁
	 * 1.sql执行释放锁定
	 * 2.更新库存工作单状态为已解锁
	 *
	 * @param skuId
	 * @param wareId
	 * @param count
	 */
	public void unLockStock(Long skuId, Long wareId, Integer count, Long taskDetailId) {
		// 1.库存解锁
		baseMapper.unLockStock(skuId, wareId, count);

		// 2.更新工作单的状态 已解锁
		WareOrderTaskDetail taskDetail = new WareOrderTaskDetail();
		taskDetail.setId(taskDetailId);
		taskDetail.setLockStatus(WareOrderTaskConstant.LockStatusEnum.UNLOCKED.getCode());
		orderTaskDetailService.updateById(taskDetail);
	}


	@Override
	public IPage queryPage(Page page, WareSku wareSku) {
		String skuName = wareSku.getSkuName();
		LambdaQueryWrapper lambdaQuery = null;
		if (StrUtil.isNotBlank(skuName)) {
			wareSku.setSkuName(null);
			lambdaQuery = Wrappers.<WareSku>lambdaQuery(wareSku).like(WareSku::getSkuName, skuName);
		} else {
			lambdaQuery = Wrappers.<WareSku>lambdaQuery(wareSku);
		}

		return page(page, lambdaQuery);
	}
}



